/** @copyright (c) 2023 周曦 @license GPL v3 */

import { buildingStyles } from "./style.building.js"
import { buildingColors } from "../src/bundle.js"
export { parseStyle, mutate, basicParsed, blockParsed, elementParsed, partParsed, resultParsed, floor, style, }

const features = {
  uses: {
    house: ['住宅'],
    commerce: ['商场'],
    office: ['办公'],
    hotel: ['酒店'],
    hospital: ['医院'],
    school: ['学校'],
    industry: ['工厂'],
    warehouse: ['仓库'],
    monument: ['纪念碑'],
  },
  tags: {
    simple: ['简单'],
    complex: ['复杂'],
    highRise: ['高层'],
    mediumRise: ['多层'],
    lowRise: ['低层'],
    vertical: ['垂直'],
    horizontal: ['水平'],
    random: ['随机'],
  }
}

type ns = number | string
type uses = keyof typeof features['uses']
type tags = keyof typeof features['tags']
type blockColors = keyof typeof buildingColors['blocks']
type lineColors = keyof typeof buildingColors['lines']

/** @see {@link basicParsed} */
interface basic {
  /** 沿XY轴的缩放距离，单值表示两个方向缩放距离相同 */
  scaleXY?: [x: ns, y: ns] | ns
  /** 围绕Z轴的旋转角度 */
  rotate?: ns
  /** 沿XY轴的移动距离 */
  moveXY?: [x: ns, y: ns]
  /** 沿Z轴的移动距离，很常用所以单独输入 */
  moveZ?: ns
}

/** @see {@link elementParsed} */
interface element extends basic {
  /** 按轴向偏缩放界生成的线元素 */
  loops?: [scaleXY: [x: ns, y: ns] | ns, color?: lineColors]
  /** 按坐标点生成线元素 */
  lines?: [points: [x: ns, y: ns, z: ns][], color?: lineColors]
  /** 按长宽高生成块元素 */
  box?: [scale: [w: ns, h: ns, d: ns], color?: blockColors]
  /** 复制自身到坐标点位置 */
  copy?: [x: ns, y: ns, z: ns][]
}

/** `depth` `scaleXY?` `color?` `rotate?` `moveXY?` `moveZ?` @see {@link blockParsed} */
interface blockStyle extends basic {
  /** 挤出体块的高度 */
  depth: ns
  /** 挤出体块的类型与材质，由名称中的 wall 区别类型 */
  as?: blockColors
}

/** `elements` `split` `scaleXY?` `rotate?` `moveXY?` `moveZ?` @see {@link partParsed} */
interface partStyle extends basic {
  /** 仅用于标识，方便编辑 */
  id?: string
  /** `loops? | lines? | box?` `copy?` `color?` `scaleXY` `rotate?` `moveXY?` `moveZ?` */
  elements: element[]
  /** 按边的方向生成（东西或南北） */
  along?: 'we' | 'ns'
  /** 按边界阵列的参数 */
  split: {
    /** 按段数等分 */
    number?: ns
    /** 按长度组合进行划分 */
    spacing?: ns[]
    /** 每条线段的两端缩进 */
    padding?: [start: ns, end: ns] | ns
    /** 忽略的最小线段长度 */
    minLength?: ns
    /** 忽略的最大线段长度 */
    maxLength?: ns
  }
}

interface floor extends basic {
  /** 仅用于标识，方便编辑 */
  id?: string
  /** 控制挤出体块的参数 */
  blocks?: blockStyle[]
  /** 控制生成组件的参数 */
  parts?: partStyle[]
  /** 样式修改器。基本规则为 [ 间隔, 修正值 ]，当楼层除以间隔的余数为 0 时进行修正 */
  mod?: {
    /** 修正该段层数 */
    floorCount?: ns
    /** 从底部和顶部修正该段层数 */
    floorEnds?: [bottom: ns, top: ns]
    /** 修正该段的 blocks 和 parts */
    style?: [divide: number, style: floor][]
    /** 修正该段沿XY轴的缩放距离 */
    scaleXY?: [divide: number, scaleXY: [ns, ns] | ns][]
    /** 修正该段围绕Z轴的旋转角度 */
    rotate?: [divide: number, radian: ns][]
    /** 修正该段沿XY轴的移动距离 */
    moveXY?: [divide: number, moveXY: [ns, ns]][]
    /** 修正该段沿Z轴的移动距离 */
    moveZ?: [divide: number, moveZ: ns][]
  }
}

interface floorStyle extends floor {
  /** 层高，省略则按解析时输入的层高 */
  height?: ns
  /** 按名称提取预设参数到 floor.blocks */
  styles?: floor[],
}

interface section extends basic {
  /** `height?` `styles? | blocks? | parts?` `mod?` `scaleXY?` `rotate?` `moveXY?` `moveZ?` */
  floors: floorStyle[]
  /** 控制原型组件生成的参数 */
  parts?: partStyle[]
  /** 段高，可省略其中一段表示按剩余高度 */
  height?: ns
}

interface style {
  /** 控制每段生成的参数组合 */
  sections: section[]
  /** 用于解析 ns 的单位变量 */
  units?: { [key: string]: number }
  tags?: tags[]
  uses?: uses[]
}

/** @see {@link basic} */
interface basicParsed {
  scaleXY: [x: number, y: number]
  rotate: number
  moveXY: [x: number, y: number]
  moveZ: number
}

/** @see {@link partStyle} */
interface partParsed extends basicParsed {
  elements: elementParsed[]
  split: {
    number?: number
    spacing?: number[]
    padding: [start: number, end: number] | 0
    minLength: number
    maxLength: number
  }
  along: partStyle['along']
}

/** @see {@link element} */
interface elementParsed extends basicParsed {
  loops?: [scaleXY: [x: number, y: number], as: lineColors]
  lines?: [points: [x: number, y: number, z: number][], as: lineColors]
  box?: [scale: [w: number, h: number, d: number], as: blockColors]
  copy: [x: number, y: number, z: number][]
}

/** @see {@link blockStyle} */
interface blockParsed extends basicParsed {
  depth: number
  as: blockColors
}

// 解析结果按材质分类缓存，便于后续 merge
// blockParsed 和 elementParsed 必须要 moveZ 属性
// 基于建筑基底生成唯一的 Geometry，基于不同材质和每类的数量创建 instanceMesh
interface resultParsed {
  blocks: blockParsed[]
  parts: partParsed[]
}

/**
 * 根据输入的高度解析参数
 * @param name 预设样式名称，如无则使用默认的
 * @param buildingHeight 建筑高度
 * @param floorHeight 楼层高度
 * @param options.fixedFloorHeight (默认:false) 固定层高会导致顶部的标高和造型随机变化
 * @param options.fixedSplits (默认:false) 固定划分会导致沿边界阵列不整齐
 */
function parseStyle(
  name: string,
  buildingHeight: number,
  floorHeight: number,
  options: {
    fixedFloorHeight?: boolean
    fixedSplits?: boolean
  } = {}
): resultParsed {
  const style = getStyle(name)
  const result: resultParsed = { blocks: [], parts: [] }
  const params = Object.assign({ BH: buildingHeight, FH: floorHeight }, style.units)

  // 计算省略的那一处段高，保持原对象不变，缓存结果到新的数组
  const heights = style.sections.map((s) => {
    if (s.height) {
      const h = parse(s.height)
      buildingHeight -= h
      return h
    }
  })

  // 确定剩余高度（buildingHeight）后开始解析参数
  // 每段的段高和层高都可能不同，因此每段单独解析
  let elevation = 0
  for (let n = style.sections.length - 1; n >= 0; n--) {
    const s = style.sections[n]
    const sh = params.SH = heights[n] || buildingHeight
    // 解析 sections.parts
    if (s.parts) parsePartsTo(result.parts, s.parts, elevation)

    // 解析 sections.floors
    s.floors.forEach(f => {
      let fh: number, fn: number
      if (options.fixedFloorHeight) {
        fh = params.FH = f.height ? parse(f.height) : floorHeight
        fn = Math.floor(sh / fh)
      } else {
        fh = f.height ? parse(f.height) : floorHeight
        fn = Math.floor(sh / fh)
        fh = params.FH = sh / fn
      }
      const mod = f.mod
      let range: [number, number] = [0, fn]
      if (mod) {
        if (mod.floorCount) {
          range = [0, parse(mod.floorCount)]
        } else if (mod.floorEnds) {
          range = [parse(mod.floorEnds[0]), fn + parse(mod.floorEnds[1])]
        }
      }

      // 解析参数，初始标高为 0
      const floorBlocks: blockParsed[] = []
      const floorParts: partParsed[] = []
      if (f.styles) {
        f.styles.forEach(s => {
          if (s.blocks) parseBlocksTo(floorBlocks, s.blocks, 0)
          if (s.parts) parsePartsTo(floorParts, s.parts, 0)
        })
      } else {
        if (f.blocks) parseBlocksTo(floorBlocks, f.blocks, 0)
        if (f.parts) parsePartsTo(floorParts, f.parts, 0)
      }

      // 遍历每一层，按标高修改参数的 moveZ
      for (let i = range[0]; i < range[1]; i++) {
        const z = elevation + i * fh
        let currentBlocks = setZ(floorBlocks, z)
        let currentParts = setZ(floorParts, z)

        // 修改参数
        if (mod) {
          check(i, (style) => {
            if (style.blocks) parseBlocksTo(currentBlocks = [], style.blocks, z)
            if (style.parts) parsePartsTo(currentParts = [], style.parts, z)
          }, mod.style)
          check(i, (v) => {
            const x = parse1OR2(v);
            floorBlocks.forEach(b => b.scaleXY = x)
            floorParts.forEach(p => p.scaleXY = x)
          }, mod.scaleXY)
          check(i, (v) => {
            const x = parse(v);
            floorBlocks.forEach(b => b.rotate = x)
            floorParts.forEach(p => p.rotate = x)
          }, mod.rotate)
          check(i, (v) => {
            const x = parse1OR2(v)
            floorBlocks.forEach(b => b.moveXY = x)
            floorParts.forEach(p => p.moveXY = x)
          }, mod.moveXY)
          check(i, (v) => {
            const x = parse(v);
            // 修改已生成的标高
            floorBlocks.forEach(b => b.moveZ = x)
            floorParts.forEach(p => p.moveZ = x)
          }, mod.moveZ)
        }

        // 最后保存解析结果
        result.blocks.push(...currentBlocks)
        result.parts.push(...currentParts)
      }
    })
    elevation += sh

  }
  return result

  function setZ<T>(a: T[], z: number): T[] {
    let k: keyof T
    return a.map(x => {
      const result: { [x: string]: any } = {}
      for (k in x) result[k as string] = x[k]
      result.moveZ += z
      return result as T
    })
  }

  function parseBlocksTo(result: blockParsed[], blocks: blockStyle[], z: number): void {
    blocks.forEach(x => {
      result.push({
        depth: parse(x.depth),
        as: x.as || 'basic',
        moveZ: parse(x.moveZ) + z,
        moveXY: parse1OR2(x.moveXY),
        scaleXY: parse1OR2(x.scaleXY),
        rotate: parse(x.rotate)
      })
    })
  }

  // 将样式中 parts 解析到 styleParsed.parts
  function parsePartsTo(a: partParsed[], parts: partStyle[], z: number): void {
    parts.forEach(x => {
      a.push({
        elements: parseElements(x.elements),
        split: parseSplit(x.split),
        moveZ: parse(x.moveZ) + z,
        moveXY: parse1OR2(x.moveXY),
        scaleXY: parse1OR2(x.scaleXY),
        rotate: parse(x.rotate),
        along: x.along
      })
    })

    function parseSplit(s: partStyle['split']): partParsed['split'] {
      const result: partParsed['split'] = {
        padding: parse1OR2(s.padding),
        minLength: parse(s.minLength),
        maxLength: parse(s.maxLength)
      }
      if (s.number) {
        result.number = parse(s.number)
      } else if (s.spacing) {
        result.spacing = s.spacing.map(parse)
      }
      return result
    }

    function parseElements(a: partStyle['elements']): partParsed['elements'] {
      return a.map(x => {
        const result: elementParsed = {
          copy: x.copy ? x.copy.map(p3D => p3D.map(parse) as [number, number, number]) : [],
          moveZ: parse(x.moveZ),
          moveXY: parse1OR2(x.moveXY),
          scaleXY: parse1OR2(x.scaleXY),
          rotate: parse(x.rotate)
        }
        if (x.lines) {
          result.lines = [x.lines[0].map(p3D => p3D.map(parse) as [number, number, number]), x.lines[1] || 'basic']
        } else if (x.box) {
          result.box = [x.box[0].map(parse) as [number, number, number], x.box[1] || 'basic']
        } else if (x.loops) {
          result.loops = [parse1OR2(x.loops[0]), x.loops[1] || 'basic']
        }
        return result
      })
    }
  }

  function check<T>(n: number, correct: (v: T) => void, modParsed?: [number, T][]): void {
    if (modParsed) {
      modParsed.forEach((a) => {
        let u, s
        if (Array.isArray(a[0])) {
          u = a[0][0]
          s = a[0][1]
        } else {
          u = a[0]
          s = 0
        }
        if (n >= s && (n - s) % u == 0) correct(a[1])
      })
    }
  }

  function parse1OR2(x?: [ns, ns] | ns): [number, number] {
    return !x ? [0, 0] : (Array.isArray(x) ? x.map(parse) : new Array(2).fill(parse(x))) as [number, number]
  }

  /**
   * 解析简单的公式。
   * @summary 支持的符号有：加法`+`、减法`-`、乘法`*`、除法`/`、括号`(...)`、随机正负值`+-`、随机区间值`~`。支持的默认的单位有：总高`BH`、段高`SH`、层高`FH`，也可以自定义单位。
   * @example
   * 随机区间值表示为 '~1.5' 或 '2~4.5'，分别表示 '0到1.5' 或 '2到4.5' 之间的随机值。
   * 随机正负值表示为 '+-2' 或 '3+-1'，分别表示 '2或-2' 或 '2或4'。
   */
  function parse(ns?: ns): number {
    var n = 0
    if (!ns) { } else if (typeof ns == 'string') {
      // 计算随机区间值
      let s = ns.replace(
        /([\d\.]*)~([\d\.]+)/g,
        (m, a, b) => [a || 0, b].map(Number).reduce((x, y) => x + (y - x) * Math.random()).toString()
      )
      // 计算单位值
      const regexp = new RegExp(Object.keys(params).map(k => `([\\d\\.]*${k})`).join('|'), 'g')
      s = s.replace(regexp, ((m) => {
        const n = /[\d\.]+/.exec(m)
        return (n ? Number(n[0]) * params[m.slice(n[0].length)] : params[m]).toString()
      }))
      // 确保没有单位
      s = s.replace(/[a-z]/ig, '')
      // 计算括号部分
      s = parseBrackets(s).replace(/[\(\)]/ig, '')
      // 计算公式
      n = Number(parseFomula(s))
      if (Number.isNaN(n)) {
        console.log(`[FOMULA ERROR] ${ns}`);
        n = 0
      }
    } else n = ns
    return n

    /** 将括号部分解析为数字，如没有括号则返回原字符串 */
    function parseBrackets(s: string): string {
      return s.replace(/\([^\)]+\)/g, (m) => parseFomula(parseBrackets(m.slice(1, -1))))
    }

    /** 计算不含括号仅有数字的简单公式 */
    function parseFomula(s: string): string {
      return s.replace(
        /([\d\.]+ *\* *[\d\.]+)|([\d\.]+ *\/ *[\d\.]+)|([-\d\.]+ *\+ *[\d\.]+)|([-\d\.]+ *- *[\d\.]+)|([\d\.]* *\+- *[\d\.]+)/g,
        (m, m1, m2, m3, m4, m5) => {
          let v = 0
          if (m1) {
            const a = m.split('*').map(Number)
            v = a[0] * a[1]
          } else if (m2) {
            const a = m.split('/').map(Number)
            v = a[0] / a[1]
          } else if (m3) {
            const a = m.split('+').map(Number)
            v = a[0] + a[1]
          } else if (m4) {
            const a = m.split('-').map(Number)
            v = a[0] - a[1]
          } else if (m5) {
            const a = m.split('+-').map(Number)
            v = Math.random() > 0.5 ? a[0] + a[1] : a[0] - a[1]
          }
          return v.toString()
        }
      )
    }
  }
}

function getStyle(name: string): style {
  let s: style = buildingStyles[name]
  if (!s) {
    const a = Object.keys(buildingStyles)
    const n = a[Math.floor(a.length * Math.random())]
    s = buildingStyles[n]
    console.warn(`Can not find '${name}', fall back to random style.`);
  }
  return s
}

type gene = {
  /** `段落序号` */
  section: number,
  /** 输入 `[样式名, 段落序号]` 或输入完整的 {@link section} 参数 */
  all?: [style: string, section: number] | section
  /** 输入 `[样式名, 段落序号]` 或输入完整的 {@link partStyle} 参数 */
  parts?: [style: string, section: number] | partStyle
  /** 输入 `[样式名, 段落序号]` 或输入完整的 {@link floor} 参数 */
  floors?: [style: string, section: number] | floor
}

/**
 * 根据原有样式替换段落，变异新的样式
 * @param from 原样式名称
 * @param genes 基于原样式进行 {@link gene} 替换，包含的属性有：`section` 原样式中段落序号，`all?:[样式名称，段落序号]` 进行整段替换，`parts?:[样式名称，段落序号]` 进行组件样式替换，`floors?:[样式名称，段落序号]` 进行楼层样式替换
 * @param to 变异后的样式名称名称
 */
function mutate(from: string, genes: gene[], to: string) {
  let mutateSuccess = false
  const result = deepClone(getStyle(from))
  genes.forEach(g => {
    if (result.sections[g.section]) {
      if (g.all) {
        const x = g.all
        const m = Array.isArray(x) ? getStyle(x[0]).sections[x[1]] : x
        if (m) {
          result.sections[g.section] = m
          mutateSuccess = true
        }
      }
      if (g.parts) {
        const x = g.parts
        const m = Array.isArray(x) ? getStyle(x[0]).sections[x[1]].parts : x
        if (m) {
          result.sections[g.section].parts = m
          mutateSuccess = true
        }
      }
      if (g.floors) {
        const x = g.floors
        const m = Array.isArray(x) ? getStyle(x[0]).sections[x[1]].floors : x
        if (m) {
          result.sections[g.section].floors = m
          mutateSuccess = true
        }
      }
    }
  })
  if (mutateSuccess) {
    buildingStyles[to] = result
    console.log(`[MUTATE] New style added as '${to}'`);
  }

  function deepClone(x: any): any {
    if (typeof x === 'object' && x !== null) {
      if (Array.isArray(x)) {
        return x.map(xx => deepClone(xx))
      } else {
        const clone: { [key: string]: any } = {}
        for (const k in x) clone[k] = deepClone(x[k]);
        return clone;
      }
    } else return x;
  }
}

